package com.hbzhit.modules.sys.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hbzhit.common.enums.TokenTypeEnum;
import com.hbzhit.common.exception.RRException;
import com.hbzhit.common.utils.Constant;
import com.hbzhit.common.utils.ShiroUtils;
import com.hbzhit.modules.sys.dao.SysMenuDao;
import com.hbzhit.modules.sys.dao.SysUserDao;
import com.hbzhit.modules.sys.dao.SysUserTokenDao;
import com.hbzhit.modules.sys.entity.SysConfigEntity;
import com.hbzhit.modules.sys.entity.SysMenuEntity;
import com.hbzhit.modules.sys.entity.SysUserEntity;
import com.hbzhit.modules.sys.entity.SysUserTokenEntity;
import com.hbzhit.modules.sys.service.*;
import com.hbzhit.modules.sys.vo.SysLoginVO;
import com.hbzhit.modules.sys.vo.SysMenuQueryVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * @author DING
 */
@Service("shiroService")
@Slf4j
@AllArgsConstructor
public class ShiroServiceImpl implements ShiroService {

    private final SysCaptchaService sysCaptchaService;
    private final SysUserService sysUserService;
    private final SysMenuDao sysMenuDao;
    private final SysUserDao sysUserDao;
    private final SysUserTokenDao sysUserTokenDao;
    private final SysLogService sysLogService;
    private final SysUserRoleService sysUserRoleService;
    private final SysConfigService sysConfigService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysUserEntity login(SysLoginVO form, String tokenType, String ip) {
        if (TokenTypeEnum.SYS.getValue().equals(tokenType)) {
            boolean captcha = sysCaptchaService.validate(form.getUuid(), form.getCaptcha());
            if (!captcha) {
                throw new RRException("验证码不正确");
            }
        }

        // 获取用户对象
        SysUserEntity tempUser;
        String username;
        String password;
        // 第三方登陆根据openid获取用户密码
        if (TokenTypeEnum.OPEN.getValue().equals(tokenType)) {
            if (ObjectUtil.isEmpty(form.getOpenid())) {
                throw new RRException("错误的OPENID");
            }
            tempUser = sysUserService.queryByOpenId(form.getOpenid());
            // 用户不存在或未绑定
            if (ObjectUtil.isEmpty(tempUser)) {
                throw new RRException("用户不存在或未绑定");
            }
            username = tempUser.getUsername();
            password = tempUser.getPassword();
        } else {
            tempUser = sysUserService.queryByUserName(form.getUsername());
            // 用户不存在
            if (ObjectUtil.isEmpty(tempUser)) {
                throw new RRException("用户不存在");
            }
            // 后台登陆没有权限
            if (TokenTypeEnum.SYS.getValue().equals(tokenType)
                    && CollUtil.isEmpty(tempUser.getRoleIdList())
                    && !ObjectUtil.equals(tempUser.getId(), Constant.SUPER_ADMIN)) {
                throw new RRException("无权登陆系统，请联系管理员");
            }
            username = form.getUsername();
            password = new Sha256Hash(form.getPassword(), tempUser.getSalt()).toHex();
        }
        
        // 自动登录
        boolean rememberMe = TokenTypeEnum.APP.getValue().equals(tokenType) || TokenTypeEnum.OPEN.getValue().equals(tokenType);
        AuthenticationToken token = new UsernamePasswordToken(username, password, rememberMe, ip);

        try {
            SecurityUtils.getSubject().login(token);
        } catch (IncorrectCredentialsException ex) {
            String msg = "账号或密码错误";
            // 判断错误次数，锁定用户
            try {
                int wrongTimes = tempUser.getWrongTimes() != null ? tempUser.getWrongTimes() : 0;
                SysConfigEntity config = sysConfigService.getOne(new QueryWrapper<SysConfigEntity>().eq("param_key","WRONG_TIMES"));
                int maxWrongTimes = config != null && config.getParamValue() != null ? Integer.parseInt(config.getParamValue()) : 0;
                wrongTimes += 1;
                tempUser.setWrongTimes(wrongTimes);
                msg = StrUtil.format("账号或密码错误，您还可以尝试{}次。", maxWrongTimes - wrongTimes);
                // 超过错误次数，锁定用户，次数清零
                if (wrongTimes >= maxWrongTimes) {
                    tempUser.setStatus(Constant.INT_N);
                    tempUser.setWrongTimes(0);
                    msg = "账号或密码错误，账号已被所定，请联系管理员。";
                }
                sysUserService.getBaseMapper().updateById(tempUser);
            } catch (Exception e) {
                log.warn("登陆错误次数校验程序出错，跳过此步骤。错误详情：", e);
            }
            
            // 记录日志
            sysLogService.saveLog(tempUser.getId(), "登录失败：" + msg, ip);
            
            throw new RRException(msg);
        }

        SysUserEntity user = BeanUtil.toBean(SecurityUtils.getSubject().getPrincipal(), SysUserEntity.class);

        //获取用户所属的角色列表
        List<Long> roleIdList = sysUserRoleService.queryRoleIdList(user.getId());
        user.setRoleIdList(roleIdList);

        // 错误次数清零
        user.setWrongTimes(0);
        //记录登陆ip及时间
        user.setLastLoginIp(ip);
        user.setLastLoginTime(new Date());
        sysUserService.getBaseMapper().updateById(user);

        // 记录日志
        sysLogService.saveLog(user.getId(), "用户登录", ip);
        return user;
    }

    @Override
    public Set<String> getUserPermissions(SysMenuQueryVO params) {
        List<String> permsList;
        Long userId = ShiroUtils.getUserId();
        //系统管理员，拥有最高权限
        if(userId == Constant.SUPER_ADMIN){
            List<SysMenuEntity> menuList = sysMenuDao.selectList(new LambdaQueryWrapper<>());
            permsList = new ArrayList<>(menuList.size());
            for(SysMenuEntity menu : menuList){
                permsList.add(menu.getPerms());
            }
        }else{
            permsList = sysUserDao.queryAllPerms(userId, params.getProp());
        }
        //用户权限列表
        Set<String> permsSet = new HashSet<>();
        for(String perms : permsList){
            if(StringUtils.isBlank(perms)){
                continue;
            }
            permsSet.addAll(Arrays.asList(perms.trim().split(",")));
        }
        return permsSet;
    }

    @Override
    public SysUserTokenEntity queryByToken(String token) {
        return sysUserTokenDao.queryByToken(token);
    }

    @Override
    public SysUserEntity queryUser(Long userId) {
        return sysUserDao.selectById(userId);
    }

    @Override
    public void logout(long userId) {
        SecurityUtils.getSubject().logout();
    }
}
